package com.intelligent.ispc.core.service.impl;

import com.google.common.collect.Lists;
import com.intelligent.ispc.common.mapper.BeanMapper;
import com.intelligent.ispc.common.persistence.DynamicSpecifications;
import com.intelligent.ispc.common.persistence.SearchFilter;
import com.intelligent.ispc.common.utils.ApplicationException;
import com.intelligent.ispc.common.utils.ExceptionMessages;
import com.intelligent.ispc.common.utils.StringUtils;
import com.intelligent.ispc.core.dto.GroupDto;
import com.intelligent.ispc.core.dto.ProjectDto;
import com.intelligent.ispc.core.dto.UserDto;
import com.intelligent.ispc.core.entity.Group;
import com.intelligent.ispc.core.entity.Project;
import com.intelligent.ispc.core.entity.Role;
import com.intelligent.ispc.core.entity.User;
import com.intelligent.ispc.core.repository.jpa.GroupDao;
import com.intelligent.ispc.core.repository.jpa.ProjectDao;
import com.intelligent.ispc.core.service.GroupService;
import com.intelligent.ispc.utils.Constant;
import com.intelligent.ispc.utils.PageSubList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.*;
import java.util.List;
import java.util.Map;

/**
 * Created by Can.Guan on 2016/5/3.
 */
@Service
public class GroupServiceImpl implements GroupService {
    private static Logger logger = LoggerFactory.getLogger(GroupServiceImpl.class);

    @Autowired
    private GroupDao groupDao;

    @Autowired
    private com.intelligent.ispc.core.repository.jpa.UserDao userDao;

    @Autowired
    private ProjectDao projectDao;

    @Override
    public void isExistGroupName(String name) {

        if (name == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = groupDao.findByName(name);
        if (group != null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_ALREADY_EXISTS);
        }
    }

    @Override
    public void checkGroupName(Long id, String name) {
        boolean isExist = false;
        Group group = groupDao.findById(id);
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }
        Group group1 = groupDao.findByName(name);
        if (group1 != null) {
            isExist = true;
            if (name != null && group.getName().equals(name)) {
                isExist = false;
            }
        }
        if (isExist) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_ALREADY_EXISTS);
        }
    }

    @Override
    public void saveGroup(GroupDto groupDto) {
        if (groupDto == null || groupDto.getName() == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = groupDao.findByName(groupDto.getName());

        if (group != null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_ALREADY_EXISTS);
        }

        group = new Group();
        BeanMapper.copy(groupDto, group);

        logger.debug("Saving user : {} ", groupDto);
        groupDao.save(group);
        logger.info("Save done,group = {} ", group.getName());
    }

    @Override
    public void updateGroup(GroupDto groupDto) {
        if (groupDto == null || groupDto.getId() == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = new Group();
        BeanMapper.copy(groupDto, group);

        logger.debug("Updating user : {} ", groupDto);
        groupDao.save(group);
        logger.info("Update done,group = {} ", group.getName());
    }

    @Override
    public void deleteGroup(String id) {
        if (id == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }
        Group group = groupDao.findById(Long.valueOf(id));

        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        if (group.getUsers() != null) {
            for (User user : group.getUsers()) {
                user.getGroups().remove(group);
            }
        }
        if (group.getProjects() != null) {
            for (Project project : group.getProjects()) {
                project.getGroups().remove(group);
            }
        }
        logger.debug("Deleting group : {} ", group.getName());
        groupDao.delete(group);
        logger.info("Delete done");
    }

    @Override
    public GroupDto findById(String id) {
        if (id == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        logger.debug("Find group by Id : {} " + id);
        Group group = groupDao.findById(Long.valueOf(id));

        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }
        GroupDto groupDto = new GroupDto();
        BeanMapper.copy(group, groupDto);

        logger.debug("Find done, group = {} " + group.getName());
        return groupDto;
    }

    @Override
    public GroupDto findByName(String name) {

        if (name == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        logger.debug("Finding the group by name : {} " + name);
        Group group = groupDao.findByName(name);


        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }
        GroupDto groupDto = new GroupDto();
        BeanMapper.copy(group, groupDto);

        logger.debug("Find done, group = {} " + group.getName());
        return groupDto;
    }

    @Override
    public List<GroupDto> findAllGroups() {
        logger.debug("Finding All Groups");
        List<Group> groupList = (List<Group>) groupDao.findAll();
        logger.debug("Find done, the size of the List is " + groupList.size());

        List<GroupDto> groupDtoList = Lists.newArrayList();

        for (Group group : groupList) {
            GroupDto groupDto = new GroupDto();
            BeanMapper.copy(group, groupDto);
            groupDtoList.add(groupDto);
        }

        return groupDtoList;
    }

    @Override
    public List<GroupDto> searchGroup(Map<String, Object> searchParams) {
        logger.debug("Searching role with{},{}", searchParams.keySet(), searchParams.values());
        Map<String, SearchFilter> fliters = SearchFilter.parse(searchParams);
        Specification<Group> spec = DynamicSpecifications.bySearchFilter(fliters.values(), Group.class);

        List<Group> groupList = groupDao.findAll(spec);
        logger.debug("Searching role,total elements:{}", groupList.size());

        List<GroupDto> groupDtoList = Lists.newArrayList();
        for (Group group : groupList) {
            GroupDto groupDto = new GroupDto();
            BeanMapper.copy(group, groupDto);
            groupDtoList.add(groupDto);
        }

        return groupDtoList;
    }

    @Override
    public Page<GroupDto> searchGroup(Map<String, Object> searchParams, Pageable pageable) {
        logger.debug("Searching Group with {},{}", searchParams.keySet(), searchParams.values(), pageable);

        Map<String, SearchFilter> filters = SearchFilter.parse(searchParams);
        Specification<Group> spec = DynamicSpecifications.bySearchFilter(filters.values(), Group.class);

        Page<Group> groupPage = groupDao.findAll(spec, pageable);
        logger.debug("Searching user,total elements:{}", groupPage.getTotalElements());
        List<Group> groupList = groupPage.getContent();
        List<GroupDto> groupDtoList = Lists.newArrayList();
        for (Group group : groupList) {
            GroupDto groupDto = new GroupDto();

            BeanMapper.copy(group, groupDto);
            groupDtoList.add(groupDto);
        }

        Page<GroupDto> pageGroupDto = new PageImpl<GroupDto>(groupDtoList, pageable, groupPage.getTotalElements());
        return pageGroupDto;
    }

    @Override
    public void assignUsersForGroup(String groupId, List<String> userIds) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }
        Group group = groupDao.findById(Long.valueOf(groupId));

        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        User user = new User();
        for (String userId : userIds) {
            if (userId == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
            }
            user = userDao.findById(Long.valueOf(userId));
            if (user == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
            }

            user.getGroups().add(group);
            group.getUsers().add(user);
        }

        logger.debug("Assigning users for group");
        groupDao.save(group);
        logger.debug("Assign done");

    }

    @Override
    public void deleteUsersForGroup(String groupId, List<String> userIds) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }
        Group group = groupDao.findById(Long.valueOf(groupId));
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }
        User user = new User();
        for (String userId : userIds) {
            if (userId == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
            }
            user = userDao.findById(Long.valueOf(userId));
            if (user == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
            }
            logger.debug("Deleting users for group, groupIds = {} " + groupId);
            logger.debug("The size of userIds = {} " + userIds.size());
            user.getGroups().remove(group);
            group.getUsers().remove(user);
            logger.debug("Delete done");
        }
    }

    @Override
    public List<UserDto> findUsersForGroup(String groupId) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = groupDao.findById(Long.valueOf(groupId));
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        logger.debug("Finding users for group Id : {} " + groupId);
        List<UserDto> userDtos = Lists.newArrayList();
        for (User user : group.getUsers()) {
            UserDto userDto = new UserDto();
            BeanMapper.copy(user, userDto);
            userDtos.add(userDto);
        }
        logger.debug("Find done, the size of users : {} " + userDtos.size());
        return userDtos;
    }

    @Override
    public List<UserDto> findUsersForNotInThisGroup(String groupId) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = groupDao.findById(Long.valueOf(groupId));
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        logger.debug("Finding users for all : {} " + groupId);

        Specification<User> spec = DynamicSpecifications.bySearchFilter(null, User.class);
        List<User> userList = userDao.findAll(spec);

        for (User user : group.getUsers()) {
            if (userList.contains(user)) {
                userList.remove(user);
            }
        }
        List<UserDto> userDtos = Lists.newArrayList();
        for (User user : userList) {
            UserDto userDto = new UserDto();
            BeanMapper.copy(user, userDto);
            userDtos.add(userDto);
        }
        return userDtos;
    }

    @Override
    public Page<UserDto> findUsersForGroup(String groupId, Pageable pageable) {
        List<UserDto> userDtos = this.findUsersForGroup(groupId);
        int count = userDtos.size();
        Page<UserDto> pageDto = new PageImpl<UserDto>(PageSubList.getPageList(userDtos, pageable.getPageNumber(), pageable.getPageSize(), count), pageable, count);
        return pageDto;
    }

    @Override
    public void assignProjectsForGroup(String groupId, List<String> projectIds) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }
        Group group = groupDao.findById(Long.valueOf(groupId));

        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        Project project = new Project();
        for (String projectId : projectIds) {
            if (projectId == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
            }
            project = projectDao.findById(Long.valueOf(projectId));
            if (project == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
            }

            project.getGroups().add(group);
            group.getProjects().add(project);
        }

        logger.debug("Assigning projects for group");
        groupDao.save(group);
        logger.debug("Assign done");
    }

    @Override
    public void deleteProjectsForGroup(String groupId, List<String> projectIds) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }
        Group group = groupDao.findById(Long.valueOf(groupId));
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }
        Project project = new Project();
        for (String projectId : projectIds) {
            if (projectId == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
            }
            project = projectDao.findById(Long.valueOf(projectId));
            if (project == null) {
                throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
            }
            logger.debug("Deleting projects for group, groupIds = {} " + groupId);
            logger.debug("The size of projectIds = {} " + projectIds.size());
            project.getGroups().remove(group);
            group.getProjects().remove(project);
            logger.debug("Delete done");
        }
    }

    @Override
    public List<ProjectDto> findProjectsForGroup(String groupId) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = groupDao.findById(Long.valueOf(groupId));
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        logger.debug("Finding projects for group Id : {} " + groupId);
        List<ProjectDto> projectDtos = Lists.newArrayList();
        for (Project project : group.getProjects()) {
            ProjectDto projectDto = new ProjectDto();
            BeanMapper.copy(project, projectDto);
            projectDtos.add(projectDto);
        }
        logger.debug("Find done, the size of projects : {} " + projectDtos.size());
        return projectDtos;
    }

    @Override
    public List<ProjectDto> findProjectsForNotInThisGroup(String groupId) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = groupDao.findById(Long.valueOf(groupId));
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        logger.debug("Finding projects for all : {} " + groupId);

        Specification<Project> spec = DynamicSpecifications.bySearchFilter(null, Project.class);
        List<Project> projectList = projectDao.findAll(spec);
        for (Project project : group.getProjects()) {
            if (projectList.contains(project)) {
                projectList.remove(project);
            }
        }
        List<ProjectDto> projectDtos = Lists.newArrayList();
        for (Project project : projectList) {
            ProjectDto projectDto = new ProjectDto();
            BeanMapper.copy(project, projectDto);
            projectDtos.add(projectDto);
        }
        return projectDtos;
    }

    @Override
    public Page<ProjectDto> findProjectsForGroup(String groupId, Pageable pageable) {
        List<ProjectDto> projectDtos = this.findProjectsForGroup(groupId);
        int count = projectDtos.size();
        Page<ProjectDto> pageDto = new PageImpl<ProjectDto>(PageSubList.getPageList(projectDtos, pageable.getPageNumber(), pageable.getPageSize(), count), pageable, count);
        return pageDto;
    }

    @Override
    public GroupDto findUsersAndProjectsForGroup(String groupId) {
        if (groupId == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        Group group = groupDao.findById(Long.valueOf(groupId));
        if (group == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        logger.debug("Finding users and project for groupId = {} " + groupId);
        GroupDto groupDto = new GroupDto();
        BeanMapper.copy(group, groupDto);
        groupDto.setUserDtos(this.findUsersForGroup(groupId));
        groupDto.setProjectDtos(this.findProjectsForGroup(groupId));
        logger.debug("Find done");
        return groupDto;
    }

    @Override
    public Page<UserDto> findUsersForGroupByName(Map<String, Object> searchParams, Pageable pageable) {

        Map<String, Object> filters = SearchFilter.parseParam(searchParams);

        Specification<User> spec = new Specification<User>() {

            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

                ListJoin groupListJoin = root.joinList("groups", JoinType.LEFT);
                List<Predicate> predicates = Lists.newArrayList();

                if (filters.containsKey("groupId") && filters.get("groupId") != null) {
                    predicates.add(cb.equal(groupListJoin.get("id").as(Long.class), filters.get("groupId")));
                }

                if (filters.containsKey("name") && filters.get("name") != null) {
                    predicates.add(cb.like(cb.lower(root.get("loginName")), "%" + filters.get("name").toString().toLowerCase() + "%"));
                }
                if (!predicates.isEmpty()) {

                    query.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
                }

                return query.getRestriction();
            }
        };

        Page<User> userPage = userDao.findAll(spec, pageable);
        List<User> userList = userPage.getContent();
        logger.debug("Searching user,total elements:{}", userPage.getTotalElements());
        List<UserDto> userDtoList = Lists.newArrayList();
        for (User user : userList) {
            UserDto userDto = new UserDto();

            BeanMapper.copy(user, userDto);
            userDtoList.add(userDto);
        }

        Page<UserDto> userDtoPage = new PageImpl<UserDto>(userDtoList, pageable, userPage.getTotalElements());
        return userDtoPage;
    }

    @Override
    public Page<ProjectDto> findProjectsForGroupByName(Map<String, Object> searchParams, Pageable pageable) {

        Map<String, Object> filters = SearchFilter.parseParam(searchParams);
        Specification<Project> spec = new Specification<Project>() {

            public Predicate toPredicate(Root<Project> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

                ListJoin groupListJoin = root.joinList("groups", JoinType.LEFT);
                List<Predicate> predicates = Lists.newArrayList();

                if (filters.containsKey("groupId") && filters.get("groupId") != null) {

                    predicates.add(cb.equal(groupListJoin.get("id").as(Long.class), filters.get("groupId")));
                }

                if (filters.containsKey("name") && filters.get("name") != null) {
                    predicates.add(cb.like(cb.lower(root.get("name")), "%" + filters.get("name").toString().toLowerCase() + "%"));
                }
                if (!predicates.isEmpty()) {

                    query.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
                }

                return query.getRestriction();
            }
        };

        Page<Project> projectPage = projectDao.findAll(spec, pageable);
        List<Project> projectList = projectPage.getContent();
        logger.debug("Searching project,total elements:{}", projectPage.getTotalElements());
        List<ProjectDto> projectDtoList = Lists.newArrayList();
        for (Project project : projectList) {
            ProjectDto projectDto = new ProjectDto();

            BeanMapper.copy(project, projectDto);
            projectDtoList.add(projectDto);
        }

        Page<ProjectDto> projectDtoPage = new PageImpl<ProjectDto>(projectDtoList, pageable, projectPage.getTotalElements());
        return projectDtoPage;
    }

    @Override
    public List<GroupDto> findGroupsForUser(String userId) {
        if (StringUtils.isBlank(userId)) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_PARAMETER_INPUT_INVALID);
        }

        User user = userDao.findById(Long.valueOf(userId));
        if (user == null) {
            throw new ApplicationException(ExceptionMessages.EXCEPTION_OBJECT_DOES_NOT_EXIST);
        }

        logger.debug("Finding groups for user Id : {} " + userId);
        List<GroupDto> groupDtoList = Lists.newArrayList();
        boolean isAdministrator = false;
        for (Role role : user.getRoles()) {
            if (Constant.ROLE_ADMINISTRATOR.equals(role.getName())) {
                GroupDto groupDto = new GroupDto();
                groupDto.setAdministrator(true);
                groupDtoList.add(groupDto);
                isAdministrator = true;
                break;
            }
        }

        if (!isAdministrator) {
            for (Group group : user.getGroups()) {
                GroupDto groupDto = new GroupDto();
                BeanMapper.copy(group, groupDto);
                groupDtoList.add(groupDto);
            }
            logger.debug("Find done, the size of group ids : {} " + groupDtoList.size());
        }
        return groupDtoList;
    }
}
